using System.Collections.Generic;
using Lucene.Net.Documents;
using Lucene.Net.Index;

namespace SpellCenter.Core
{
    /// <summary>
    /// This is an in-memory version of FragmentMovementTracker. 
    /// FragmentMovementTracker looks into the index, this keeps
    /// all information in memory so it can be changed easily and 
    /// also easily persisted to Lucene.
    /// </summary>
    sealed internal class MetaDocumentModel
    {
        private readonly Dictionary<string, Dictionary<long, int>> _starts;
        private readonly Dictionary<string, Dictionary<long, int>> _ends;

        public MetaDocumentModel()
        {
            _starts = new Dictionary<string, Dictionary<long, int>>();
            _ends = new Dictionary<string, Dictionary<long, int>>();
        }

        /// <summary>
        /// Factory method to get a MetaDocumentModel object from a given 
        /// FragmentMovementTracker disk handle.
        /// </summary>
        /// <param name="document">the disk handle to create the model from</param>
        /// <returns></returns>
        public static MetaDocumentModel GetFromMeta(Document document)
        {
            MetaDocumentModel ret = new MetaDocumentModel();
            foreach (Field field in document.Fields())
            {
                string name = field.Name(), prefix, id;
                if(name == FragmentMovementTracker.ID) 
                    continue;
                int offset;
                FragmentMovementTracker.DisAssembleString(name, out prefix, out id, out offset);
                if(prefix == FragmentMovementTracker.START_PFX)
                {
                    ret.SetStart(id, offset, int.Parse(field.StringValue()));
                }
                else
                {
                    ret.SetEnd(id, offset, int.Parse(field.StringValue()));
                }
            }
            return ret;
        }

      public void CreateDocument(IndexWriter writer)
      {
        writer.AddDocument(GetDocument());
      }

      public int GetStart(string fname, long start) { return _starts[fname][start]; }
        public void SetStart(string fname, long start, int val) { SetDictValue(_starts, fname, start, val); }
        public int GetEnd(string fname, long end) { return _ends[fname][end]; }
        public void SetEnd(string fname, long end, int val) { SetDictValue(_ends, fname, end, val); }

        private static void SetDictValue(IDictionary<string, Dictionary<long, int>> dict, string fname, long key, int val)
        {
            Get(dict, fname)[key] = val;
        }

        public Document GetDocument()
        {
            Document ret = new Document();
            AddFieldsFromDictionary(ret, _starts, FragmentMovementTracker.GetStartName);
            AddFieldsFromDictionary(ret, _ends, FragmentMovementTracker.GetEndName);
            ret.Add(new Field(FragmentMovementTracker.ID, FragmentMovementTracker.ID, Field.Store.YES, Field.Index.UN_TOKENIZED));
            return ret;
        }

        private delegate string IdComposer(string id, long num);
        private static void AddFieldsFromDictionary(Document doc, IEnumerable<KeyValuePair<string, Dictionary<long, int>>> dict, IdComposer fx)
        {
            foreach (KeyValuePair<string, Dictionary<long, int>> file in dict)
            {
                foreach (KeyValuePair<long, int> pair in file.Value)
                {
                    doc.Add(GetField(fx(file.Key, pair.Key), pair.Value));
                }
            }
        }

        private static Field GetField(string key, int val)
        {
            return new Field(key, "" + val, Field.Store.YES, Field.Index.UN_TOKENIZED);
        }
        private static Dictionary<long, int> Get(IDictionary<string,Dictionary<long,int>> dict, string key)
        {
            if(!dict.ContainsKey(key))
            {
                dict[key] = new Dictionary<long, int>();
            }
            return dict[key];
        }

        public void AddOffset(string filename, long start, int diff)
        {
            AddOffsetToDocuments(Get(_starts, filename), start, diff);
            AddOffsetToDocuments(Get(_ends, filename), start, diff); // We don't need an end b/c end > start always
        }

        private static void AddOffsetToDocuments(IDictionary<long, int> dict, long start, int diff)
        {
            // by creating a new List<KeyValuePair<long, int>> we copy the keys, bypassing the 
            //  constraints of the IEnumerator interface (changing the collection)
            foreach (KeyValuePair<long, int> pair in new List<KeyValuePair<long, int>>(dict))
            {
                if(start < pair.Key)
                {
                    dict[pair.Key] = pair.Value + diff;
                }
            }
        }
    }
}